﻿// Copyright © 2008, EMC Corporation.
// Redistribution and use in source and binary forms, with or without modification, 
// are permitted provided that the following conditions are met:
//
//     + Redistributions of source code must retain the above copyright notice, 
//       this list of conditions and the following disclaimer.
//     + Redistributions in binary form must reproduce the above copyright 
//       notice, this list of conditions and the following disclaimer in the 
//       documentation and/or other materials provided with the distribution.
//     + The name of EMC Corporation may not be used to endorse or promote 
//       products derived from this software without specific prior written 
//       permission.
//
//      THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
//      "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 
//      TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
//      PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 
//      BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
//      CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
//      SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
//      INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
//      CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
//      ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
//      POSSIBILITY OF SUCH DAMAGE.

using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.IO;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Security.Cryptography;
using System.Reflection;
using System.Xml;

namespace EsuSysMgmtApiLib.Rest
{
    public class EsuRestApi : EsuApi
    {
        private static readonly Regex OBJECTID_EXTRACTOR = new Regex("/[0-9a-zA-Z]+/objects/([0-9a-f]{44})");
        private static TraceSource log = new TraceSource("EsuRestApi");
        private static Encoding headerEncoder = Encoding.GetEncoding("iso-8859-1");


        private byte[] secret;
        private string context = "/rest";

        /// <summary>
        /// The context root of the web services on the server.  By default,
        /// the context root is "/rest".
        /// </summary>
        public string Context
        {
            get { return context; }
            set { context = value; }
        }

        private string host;

        /// <summary>
        /// The hostname or IP address of the ESU server.
        /// </summary>
        public string Host
        {
            get { return host; }
        }

        private int port;

        /// <summary>
        /// The port number of the ESU server.  Usually, this is 80 for
        /// HTTP and 443 for HTTPS.
        /// </summary>
        public int Port
        {
            get { return port; }
            set { port = value; }
        }

        private string uid;

        /// <summary>
        /// The user ID to used in requests
        /// </summary>
        public string Uid
        {
            get { return uid; }
        }

        private string protocol;

        /// <summary>
        /// The protocol used.  Generally is "http" or "https".
        /// </summary>
        public string Protocol
        {
            get { return protocol; }
            set { protocol = value; }
        }

        /// <summary>
        /// Creates a new EsuRestApi object
        /// </summary>
        /// <param name="host">The hostname or IP address of the ESU server</param>
        /// <param name="port">The port on the server to communicate with. Generally this is 80 for HTTP and 443 for HTTPS.</param>
        /// <param name="uid">The username to use when connecting to the server</param>
        /// <param name="sharedSecret">The Base64 encoded shared secret to use to sign requests to the server.</param>
        public EsuRestApi(string host, int port, string uid, string sharedSecret)
        {
            this.host = host;
            this.port = port;
            this.uid = uid;
            this.secret = Convert.FromBase64String(sharedSecret);
            if (port == 443)
            {
                protocol = "https";
            }
            else
            {
                protocol = "http";
            }
        }

        /// <summary>
        /// By default, when posting data .Net will not post a body on the first 
        /// request and tell the server it expects "100 Continue".  After getting
        /// "100 Continue" it then posts the data.  This is done to avoid sending
        /// the body when redirects or errors occur but can be detrimental to
        /// performance since more round trips are required.
        /// </summary>
        /// <param name="enabled">Set to true to enable continue behavior.  Set to false to disable it.</param>
        public void Set100Continue(bool enabled)
        {
            ServicePointManager.FindServicePoint(buildUrl("/objects")).Expect100Continue = enabled;
        }

        #region SysManagementAPIs
        ////NODE/RMG Details
        /// <summary>
        /// Get list of nodes in the Atmos Cloud
        /// </summary>
        /// <param name="SysAdminP">The password of the username invoking the call</param>
        /// <param name="SysAdminU">The username invoking the call</param>
        /// <returns>string variable (XML format)</returns>
        public string GetNodeList(string RMGname, string SysAdminU, string SysAdminP)
        {
            HttpWebResponse resp = null;
            try
            {
                string resource = "/sysmgmt/rmgs/" + RMGname + "/nodes";
                Uri u = buildUrl(resource);
                HttpWebRequest con = (HttpWebRequest)WebRequest.Create(u);
                ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };

                // Build headers
                Dictionary<string, string> headers = new Dictionary<string, string>();

                headers.Add("x-atmos-systemadminpassword", SysAdminP);
                headers.Add("x-atmos-systemadmin", SysAdminU);
                headers.Add("x-atmos-authtype", "password");


                // Add date
                string dateHeader = DateTime.Now.ToUniversalTime().ToString("r");
                log.TraceEvent(TraceEventType.Verbose, 0, "Date: " + dateHeader);
                headers.Add("Date", dateHeader);

                // Sign request
                signRequest(con, "GET", resource, headers);

                // Check response
                resp = (HttpWebResponse)con.GetResponse();
                int statInt = (int)resp.StatusCode;
                if (statInt > 299)
                {
                    handleError(resp);
                }

                byte[] response = readResponse(resp, null);
                string responseStr = Encoding.UTF8.GetString(response);
                log.TraceEvent(TraceEventType.Verbose, 0, "Response: " + responseStr);
                return responseStr;
            }
            catch (UriFormatException e)
            {
                throw new EsuException("Invalid URL", e);
            }
            catch (IOException e)
            {
                throw new EsuException("Error connecting to server", e);
            }
            catch (WebException e)
            {
                if (e.Response != null)
                {
                    handleError((HttpWebResponse)e.Response);
                }
                else
                {
                    throw new EsuException("Error executing request: " + e.Message, e);
                }
            }
            finally
            {
                if (resp != null)
                {
                    resp.Close();
                }
            }
            return null;;
        }
        /// <summary>
        /// Get information about the underlying NODE in an RMG
        /// </summary>
        /// <param name="RMGname">The name of the RMG</param>
        /// <param name="NODEname">The name of the Node within the RMG</param>
        /// <param name="SysAdminP">The password of the username invoking the call</param>
        /// <param name="SysAdminU">The username invoking the call</param>
        /// <returns>string variable (XML format)</returns>
        public string GetNodeInfo(string RMGname, string NODEname, string SysAdminU, string SysAdminP)
        {
            HttpWebResponse resp = null;
            try
            {
                string resource = "/sysmgmt/rmgs/" + RMGname + "/nodes/" + NODEname;
                Uri u = buildUrl(resource);
                HttpWebRequest con = (HttpWebRequest)WebRequest.Create(u);
                ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };

                // Build headers
                Dictionary<string, string> headers = new Dictionary<string, string>();

                headers.Add("x-atmos-systemadminpassword", SysAdminP);
                headers.Add("x-atmos-systemadmin", SysAdminU);
                headers.Add("x-atmos-authtype", "password");


                // Add date
                string dateHeader = DateTime.Now.ToUniversalTime().ToString("r");
                log.TraceEvent(TraceEventType.Verbose, 0, "Date: " + dateHeader);
                headers.Add("Date", dateHeader);

                // Sign request
                signRequest(con, "GET", resource, headers);

                // Check response
                resp = (HttpWebResponse)con.GetResponse();
                int statInt = (int)resp.StatusCode;
                if (statInt > 299)
                {
                    handleError(resp);
                }

                byte[] response = readResponse(resp, null);
                string responseStr = Encoding.UTF8.GetString(response);
                log.TraceEvent(TraceEventType.Verbose, 0, "Response: " + responseStr);
                return responseStr;
            }
            catch (UriFormatException e)
            {
                throw new EsuException("Invalid URL", e);
            }
            catch (IOException e)
            {
                throw new EsuException("Error connecting to server", e);
            }
            catch (WebException e)
            {
                if (e.Response != null)
                {
                    handleError((HttpWebResponse)e.Response);
                }
                else
                {
                    throw new EsuException("Error executing request: " + e.Message, e);
                }
            }
            finally
            {
                if (resp != null)
                {
                    resp.Close();
                }
            }
            return null;;
        }
        //END NODE/RMG Details

        //MANAGING SUBTENANTS
        /// <summary>
        /// Get information about the underlying RMG
        /// </summary>
        /// <param name="rmgName">The name of the RMG</param>
        /// <param name="SysAdminP">The password of the username invoking the call</param>
        /// <param name="SysAdminU">The username invoking the call</param>
        /// <returns>string variable (XML format)</returns>
        public string GetRMGInfo(string rmgName, string SysAdminU, string SysAdminP)
        {
            HttpWebResponse resp = null;
            try
            {
                string resource = "/sysmgmt/rmgs/" + rmgName;
                Uri u = buildUrl(resource);
                HttpWebRequest con = (HttpWebRequest)WebRequest.Create(u);
                ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };

                // Build headers
                Dictionary<string, string> headers = new Dictionary<string, string>();

                headers.Add("x-atmos-systemadminpassword", SysAdminP);
                headers.Add("x-atmos-systemadmin", SysAdminU);
                headers.Add("x-atmos-authtype", "password");


                // Add date
                string dateHeader = DateTime.Now.ToUniversalTime().ToString("r");
                log.TraceEvent(TraceEventType.Verbose, 0, "Date: " + dateHeader);
                headers.Add("Date", dateHeader);

                // Sign request
                signRequest(con, "GET", resource, headers);

                // Check response
                resp = (HttpWebResponse)con.GetResponse();
                int statInt = (int)resp.StatusCode;
                if (statInt > 299)
                {
                    handleError(resp);
                }

                byte[] response = readResponse(resp, null);
                string responseStr = Encoding.UTF8.GetString(response);
                log.TraceEvent(TraceEventType.Verbose, 0, "Response: " + responseStr);
                return responseStr;
            }
            catch (UriFormatException e)
            {
                throw new EsuException("Invalid URL", e);
            }
            catch (IOException e)
            {
                throw new EsuException("Error connecting to server", e);
            }
            catch (WebException e)
            {
                if (e.Response != null)
                {
                    handleError((HttpWebResponse)e.Response);
                }
                else
                {
                    throw new EsuException("Error executing request: " + e.Message, e);
                }
            }
            finally
            {
                if (resp != null)
                {
                    resp.Close();
                }
            }
            return null;;
        }
        /// <summary>
        /// Get information about RMGs in your cloud
        /// </summary>
        /// <param name="SysAdminP">The password of the username invoking the call</param>
        /// <param name="SysAdminU">The username invoking the call</param>
        /// <returns>string variable (XML format)</returns>
        public string GetRMGList(string SysAdminU, string SysAdminP)
        {
            HttpWebResponse resp = null;
            try
            {
                string resource = "/sysmgmt/rmgs/";
                Uri u = buildUrl(resource);
                HttpWebRequest con = (HttpWebRequest)WebRequest.Create(u);
                ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };

                // Build headers
                Dictionary<string, string> headers = new Dictionary<string, string>();

                headers.Add("x-atmos-systemadminpassword", SysAdminP);
                headers.Add("x-atmos-systemadmin", SysAdminU);
                headers.Add("x-atmos-authtype", "password");


                // Add date
                string dateHeader = DateTime.Now.ToUniversalTime().ToString("r");
                log.TraceEvent(TraceEventType.Verbose, 0, "Date: " + dateHeader);
                headers.Add("Date", dateHeader);

                // Sign request
                signRequest(con, "GET", resource, headers);

                // Check response
                resp = (HttpWebResponse)con.GetResponse();
                int statInt = (int)resp.StatusCode;
                if (statInt > 299)
                {
                    handleError(resp);
                }

                byte[] response = readResponse(resp, null);
                string responseStr = Encoding.UTF8.GetString(response);
                log.TraceEvent(TraceEventType.Verbose, 0, "Response: " + responseStr);
                return responseStr;
            }
            catch (UriFormatException e)
            {
                throw new EsuException("Invalid URL", e);
            }
            catch (IOException e)
            {
                throw new EsuException("Error connecting to server", e);
            }
            catch (WebException e)
            {
                if (e.Response != null)
                {
                    handleError((HttpWebResponse)e.Response);
                }
                else
                {
                    throw new EsuException("Error executing request: " + e.Message, e);
                }
            }
            finally
            {
                if (resp != null)
                {
                    resp.Close();
                }
            }
            return null;;
        }
        /// <summary>
        /// List Subtenants
        /// </summary>
        /// <param name="tenant">Tenant Name</param>
        /// <param name="tenantpassword">The password of the username invoking the call</param>
        /// <param name="tenantadmin">The username invoking the call</param>
        /// <returns>string variable (XML format)</returns>
        public string GetSubTenantList(string tenant, string tenantadmin, string tenantpassword)
        {
            HttpWebResponse resp = null;
            try
            {
                string resource = "/sysmgmt/tenants/" + tenant + "/subtenants/";
                Uri u = buildUrl(resource);
                HttpWebRequest con = (HttpWebRequest)WebRequest.Create(u);
                ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };

                // Build headers
                Dictionary<string, string> headers = new Dictionary<string, string>();

                headers.Add("x-atmos-tenantadminpassword", tenantpassword);
                headers.Add("x-atmos-tenantadmin", tenantadmin);
                headers.Add("x-atmos-authtype", "password");
                headers.Add("x-atmos-authsource", "local");


                // Add date
                string dateHeader = DateTime.Now.ToUniversalTime().ToString("r");
                log.TraceEvent(TraceEventType.Verbose, 0, "Date: " + dateHeader);
                headers.Add("Date", dateHeader);

                // Sign request
                signRequest(con, "GET", resource, headers);

                // Check response
                resp = (HttpWebResponse)con.GetResponse();
                int statInt = (int)resp.StatusCode;
                if (statInt > 299)
                {
                    handleError(resp);
                }

                byte[] response = readResponse(resp, null);
                string responseStr = Encoding.UTF8.GetString(response);
                log.TraceEvent(TraceEventType.Verbose, 0, "Response: " + responseStr);
                return responseStr;
            }
            catch (UriFormatException e)
            {
                throw new EsuException("Invalid URL", e);
            }
            catch (IOException e)
            {
                throw new EsuException("Error connecting to server", e);
            }
            catch (WebException e)
            {
                if (e.Response != null)
                {
                    handleError((HttpWebResponse)e.Response);
                }
                else
                {
                    throw new EsuException("Error executing request: " + e.Message, e);
                }
            }
            finally
            {
                if (resp != null)
                {
                    resp.Close();
                }
            }
            return null;
        }
        /// <summary>
        /// Read Subtenants
        /// </summary>
        /// <param name="subtenant">sub tenant name</param>
        /// <param name="tenant">Tenant Name</param>
        /// <param name="tenantpassword">The password of the username invoking the call</param>
        /// <param name="tenantadmin">The username invoking the call</param>
        /// <returns>string variable (XML format)</returns>
        public string ReadSubTenant(string tenant, string subtenant, string tenantadmin, string tenantpassword)
        {
            HttpWebResponse resp = null;
            try
            {
                string resource = "/sysmgmt/tenants/" + tenant + "/subtenants/" + subtenant;
                Uri u = buildUrl(resource);
                HttpWebRequest con = (HttpWebRequest)WebRequest.Create(u);
                ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };

                // Build headers
                Dictionary<string, string> headers = new Dictionary<string, string>();

                headers.Add("x-atmos-tenantadminpassword", tenantpassword);
                headers.Add("x-atmos-tenantadmin", tenantadmin);
                headers.Add("x-atmos-authtype", "password");
                headers.Add("x-atmos-authsource", "local");


                // Add date
                string dateHeader = DateTime.Now.ToUniversalTime().ToString("r");
                log.TraceEvent(TraceEventType.Verbose, 0, "Date: " + dateHeader);
                headers.Add("Date", dateHeader);

                // Sign request
                signRequest(con, "GET", resource, headers);

                // Check response
                resp = (HttpWebResponse)con.GetResponse();
                int statInt = (int)resp.StatusCode;
                if (statInt > 299)
                {
                    handleError(resp);
                }

                byte[] response = readResponse(resp, null);
                string responseStr = Encoding.UTF8.GetString(response);
                log.TraceEvent(TraceEventType.Verbose, 0, "Response: " + responseStr);
                return responseStr;
            }
            catch (UriFormatException e)
            {
                throw new EsuException("Invalid URL", e);
            }
            catch (IOException e)
            {
                throw new EsuException("Error connecting to server", e);
            }
            catch (WebException e)
            {
                if (e.Response != null)
                {
                    handleError((HttpWebResponse)e.Response);
                }
                else
                {
                    throw new EsuException("Error executing request: " + e.Message, e);
                }
            }
            finally
            {
                if (resp != null)
                {
                    resp.Close();
                }
            }
            return null;;
        }
        //END MANAGING SUBTENANTS

        //MANAGING UIDS
        /// <summary>
        /// List UIDS
        /// </summary>
        /// <param name="subtenant">sub tenant name</param>
        /// <param name="tenant">Tenant Name</param>
        /// <param name="tenantpassword">The password of the username invoking the call</param>
        /// <param name="tenantadmin">The username invoking the call</param>
        /// <returns>string variable (XML format)</returns>
        public string GetUIDList(string tenant, string subtenant, string tenantadmin, string tenantpassword)
        {
            HttpWebResponse resp = null;
            try
            {
                string resource = "/sysmgmt/tenants/" + tenant + "/subtenants/" + subtenant + "/uids";
                Uri u = buildUrl(resource);
                HttpWebRequest con = (HttpWebRequest)WebRequest.Create(u);
                ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };

                // Build headers
                Dictionary<string, string> headers = new Dictionary<string, string>();

                headers.Add("x-atmos-tenantadminpassword", tenantpassword);
                headers.Add("x-atmos-tenantadmin", tenantadmin);
                headers.Add("x-atmos-authtype", "password");
                headers.Add("x-atmos-authsource", "local");


                // Add date
                string dateHeader = DateTime.Now.ToUniversalTime().ToString("r");
                log.TraceEvent(TraceEventType.Verbose, 0, "Date: " + dateHeader);
                headers.Add("Date", dateHeader);

                // Sign request
                signRequest(con, "GET", resource, headers);

                // Check response
                resp = (HttpWebResponse)con.GetResponse();
                int statInt = (int)resp.StatusCode;
                if (statInt > 299)
                {
                    handleError(resp);
                }

                byte[] response = readResponse(resp, null);
                string responseStr = Encoding.UTF8.GetString(response);
                log.TraceEvent(TraceEventType.Verbose, 0, "Response: " + responseStr);
                return responseStr;
            }
            catch (UriFormatException e)
            {
                throw new EsuException("Invalid URL", e);
            }
            catch (IOException e)
            {
                throw new EsuException("Error connecting to server", e);
            }
            catch (WebException e)
            {
                if (e.Response != null)
                {
                    handleError((HttpWebResponse)e.Response);
                }
                else
                {
                    throw new EsuException("Error executing request: " + e.Message, e);
                }
            }
            finally
            {
                if (resp != null)
                {
                    resp.Close();
                }
            }
            return null;;
        }

        /// <summary>
        /// Read Shared Secret from UID
        /// </summary>
        /// <param name="subtenant">sub tenant name</param>
        /// <param name="tenant">Tenant Name</param>
        /// <param name="tenantpassword">The password of the username invoking the call</param>
        /// <param name="tenantadmin">The username invoking the call</param>
        /// <param name="uid">The UID to pull the shared secret for</param>
        /// <returns>string variable (XML format)</returns>
        public string ReadSharedSecret(string tenant, string subtenant, string tenantadmin, string tenantpassword, string uid)
        {
            HttpWebResponse resp = null;
            try
            {
                string resource = "/sysmgmt/tenants/" + tenant + "/subtenants/" + subtenant + "/uids/" + uid;
                Uri u = buildUrl(resource);
                HttpWebRequest con = (HttpWebRequest)WebRequest.Create(u);
                ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };

                // Build headers
                Dictionary<string, string> headers = new Dictionary<string, string>();

                headers.Add("x-atmos-tenantadminpassword", tenantpassword);
                headers.Add("x-atmos-tenantadmin", tenantadmin);
                headers.Add("x-atmos-authtype", "password");
                headers.Add("x-atmos-authsource", "local");


                // Add date
                string dateHeader = DateTime.Now.ToUniversalTime().ToString("r");
                log.TraceEvent(TraceEventType.Verbose, 0, "Date: " + dateHeader);
                headers.Add("Date", dateHeader);

                // Sign request
                signRequest(con, "GET", resource, headers);

                // Check response
                resp = (HttpWebResponse)con.GetResponse();
                int statInt = (int)resp.StatusCode;
                if (statInt > 299)
                {
                    handleError(resp);
                }

                byte[] response = readResponse(resp, null);
                string responseStr = Encoding.UTF8.GetString(response);
                log.TraceEvent(TraceEventType.Verbose, 0, "Response: " + responseStr);
                return responseStr;
            }
            catch (UriFormatException e)
            {
                throw new EsuException("Invalid URL", e);
            }
            catch (IOException e)
            {
                throw new EsuException("Error connecting to server", e);
            }
            catch (WebException e)
            {
                if (e.Response != null)
                {
                    handleError((HttpWebResponse)e.Response);
                }
                else
                {
                    throw new EsuException("Error executing request: " + e.Message, e);
                }
            }
            finally
            {
                if (resp != null)
                {
                    resp.Close();
                }
            }
            return null;;
        }
        //END MANAGING UIDS


        //MANAGING POLICIES
        /// <summary>
        /// List Policies
        /// </summary>
        /// <param name="tenant">Tenant Name</param>
        /// <param name="tenantpassword">The password of the username invoking the call</param>
        /// <param name="tenantadmin">The username invoking the call</param>
        /// <returns>string variable (XML format)</returns>
        public string GetPolicySelectorList(string tenant, string tenantadmin, string tenantpassword)
        {
            HttpWebResponse resp = null;
            try
            {
                string resource = "/sysmgmt/" + tenant + "/policyselectors";
                Uri u = buildUrl(resource);
                HttpWebRequest con = (HttpWebRequest)WebRequest.Create(u);
                ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };

                // Build headers
                Dictionary<string, string> headers = new Dictionary<string, string>();

                headers.Add("x-atmos-tenantadminpassword", tenantpassword);
                headers.Add("x-atmos-tenantadmin", tenantadmin);
                headers.Add("x-atmos-authtype", "password");
                headers.Add("x-atmos-authsource", "local");


                // Add date
                string dateHeader = DateTime.Now.ToUniversalTime().ToString("r");
                log.TraceEvent(TraceEventType.Verbose, 0, "Date: " + dateHeader);
                headers.Add("Date", dateHeader);

                // Sign request
                signRequest(con, "GET", resource, headers);

                // Check response
                resp = (HttpWebResponse)con.GetResponse();
                int statInt = (int)resp.StatusCode;
                if (statInt > 299)
                {
                    handleError(resp);
                }

                byte[] response = readResponse(resp, null);
                string responseStr = Encoding.UTF8.GetString(response);
                log.TraceEvent(TraceEventType.Verbose, 0, "Response: " + responseStr);
                return responseStr;
            }
            catch (UriFormatException e)
            {
                throw new EsuException("Invalid URL", e);
            }
            catch (IOException e)
            {
                throw new EsuException("Error connecting to server", e);
            }
            catch (WebException e)
            {
                if (e.Response != null)
                {
                    handleError((HttpWebResponse)e.Response);
                }
                else
                {
                    throw new EsuException("Error executing request: " + e.Message, e);
                }
            }
            finally
            {
                if (resp != null)
                {
                    resp.Close();
                }
            }
            return null;;
        }

        /// <summary>
        /// Get Policy Information
        /// </summary>
        /// <param name="policyselectorname">policy selector name</param>
        /// <param name="tenant">Tenant Name</param>
        /// <param name="tenantpassword">The password of the username invoking the call</param>
        /// <param name="tenantadmin">The username invoking the call</param>
        /// <returns>string variable (XML format)</returns>
        public string GetPolicySelectorInfo(string policyselectorname, string tenant, string tenantadmin, string tenantpassword)
        {
            HttpWebResponse resp = null;
            try
            {
                string resource = "/sysmgmt/" + tenant + "/policyselectors/" + policyselectorname;
                Uri u = buildUrl(resource);
                HttpWebRequest con = (HttpWebRequest)WebRequest.Create(u);
                ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };

                // Build headers
                Dictionary<string, string> headers = new Dictionary<string, string>();

                headers.Add("x-atmos-tenantadminpassword", tenantpassword);
                headers.Add("x-atmos-tenantadmin", tenantadmin);
                headers.Add("x-atmos-authtype", "password");
                headers.Add("x-atmos-authsource", "local");


                // Add date
                string dateHeader = DateTime.Now.ToUniversalTime().ToString("r");
                log.TraceEvent(TraceEventType.Verbose, 0, "Date: " + dateHeader);
                headers.Add("Date", dateHeader);

                // Sign request
                signRequest(con, "GET", resource, headers);

                // Check response
                resp = (HttpWebResponse)con.GetResponse();
                int statInt = (int)resp.StatusCode;
                if (statInt > 299)
                {
                    handleError(resp);
                }

                byte[] response = readResponse(resp, null);
                string responseStr = Encoding.UTF8.GetString(response);
                log.TraceEvent(TraceEventType.Verbose, 0, "Response: " + responseStr);
                return responseStr;
            }
            catch (UriFormatException e)
            {
                throw new EsuException("Invalid URL", e);
            }
            catch (IOException e)
            {
                throw new EsuException("Error connecting to server", e);
            }
            catch (WebException e)
            {
                if (e.Response != null)
                {
                    handleError((HttpWebResponse)e.Response);
                }
                else
                {
                    throw new EsuException("Error executing request: " + e.Message, e);
                }
            }
            finally
            {
                if (resp != null)
                {
                    resp.Close();
                }
            }
            return null;;
        }
        //END MANAGING POLICIES


        //ATMOS STORAGE CONSUMPTION
        /// <summary>
        /// Get Storage Consumption at the TENANT level
        /// </summary>
        /// <param name="tenant">Tenant Name</param>
        /// <param name="tenantpassword">The password of the username invoking the call</param>
        /// <param name="tenantadmin">The username invoking the call</param>
        /// <returns>string variable (XML format)</returns>
        public string GetStorageConsumption(string tenant, string tenantadmin, string tenantpassword)
        {
            HttpWebResponse resp = null;
            try
            {
                string resource = "/sysmgmt/tenants/" + tenant + "/scMetrics";
                Uri u = buildUrl(resource);
                HttpWebRequest con = (HttpWebRequest)WebRequest.Create(u);
                ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };

                // Build headers
                Dictionary<string, string> headers = new Dictionary<string, string>();

                headers.Add("x-atmos-tenantadminpassword", tenantpassword);
                headers.Add("x-atmos-tenantadmin", tenantadmin);
                headers.Add("x-atmos-authtype", "password");
                headers.Add("x-atmos-authsource", "local");


                // Add date
                string dateHeader = DateTime.Now.ToUniversalTime().ToString("r");
                log.TraceEvent(TraceEventType.Verbose, 0, "Date: " + dateHeader);
                headers.Add("Date", dateHeader);

                // Sign request
                signRequest(con, "GET", resource, headers);

                // Check response
                resp = (HttpWebResponse)con.GetResponse();
                int statInt = (int)resp.StatusCode;
                if (statInt > 299)
                {
                    handleError(resp);
                }

                byte[] response = readResponse(resp, null);
                string responseStr = Encoding.UTF8.GetString(response);
                log.TraceEvent(TraceEventType.Verbose, 0, "Response: " + responseStr);
                return responseStr;
            }
            catch (UriFormatException e)
            {
                throw new EsuException("Invalid URL", e);
            }
            catch (IOException e)
            {
                throw new EsuException("Error connecting to server", e);
            }
            catch (WebException e)
            {
                if (e.Response != null)
                {
                    handleError((HttpWebResponse)e.Response);
                }
                else
                {
                    throw new EsuException("Error executing request: " + e.Message, e);
                }
            }
            finally
            {
                if (resp != null)
                {
                    resp.Close();
                }
            }
            return null;;
        }

        /// <summary>
        /// Get Storage Consumption at the UID level
        /// </summary>
        /// <param name="tenant">Tenant Name</param>
        /// <param name="tenantpassword">The password of the username invoking the call</param>
        /// <param name="tenantadmin">The username invoking the call</param>
        /// <param name="uid">The UID</param>
        /// <returns>string variable (XML format)</returns>
        public string GetStorageConsumption(string tenant, string subtenant, string tenantadmin, string tenantpassword, string uid)
        {
            HttpWebResponse resp = null;
            try
            {
                string resource = "/sysmgmt/tenants/" + tenant + "/" + subtenant + "/" + uid + "/scMetrics";
                Uri u = buildUrl(resource);
                HttpWebRequest con = (HttpWebRequest)WebRequest.Create(u);
                ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };

                // Build headers
                Dictionary<string, string> headers = new Dictionary<string, string>();

                headers.Add("x-atmos-tenantadminpassword", tenantpassword);
                headers.Add("x-atmos-tenantadmin", tenantadmin);
                headers.Add("x-atmos-authtype", "password");
                headers.Add("x-atmos-authsource", "local");


                // Add date
                string dateHeader = DateTime.Now.ToUniversalTime().ToString("r");
                log.TraceEvent(TraceEventType.Verbose, 0, "Date: " + dateHeader);
                headers.Add("Date", dateHeader);

                // Sign request
                signRequest(con, "GET", resource, headers);

                // Check response
                resp = (HttpWebResponse)con.GetResponse();
                int statInt = (int)resp.StatusCode;
                if (statInt > 299)
                {
                    handleError(resp);
                }

                byte[] response = readResponse(resp, null);
                string responseStr = Encoding.UTF8.GetString(response);
                log.TraceEvent(TraceEventType.Verbose, 0, "Response: " + responseStr);
                return responseStr;
            }
            catch (UriFormatException e)
            {
                throw new EsuException("Invalid URL", e);
            }
            catch (IOException e)
            {
                throw new EsuException("Error connecting to server", e);
            }
            catch (WebException e)
            {
                if (e.Response != null)
                {
                    handleError((HttpWebResponse)e.Response);
                }
                else
                {
                    throw new EsuException("Error executing request: " + e.Message, e);
                }
            }
            finally
            {
                if (resp != null)
                {
                    resp.Close();
                }
            }
            return null;;
        }

        /// <summary>
        /// Get Storage Consumption at the SubTenantLevel
        /// </summary>
        /// <param name="tenant">Tenant Name</param>
        /// <param name="subtenant">Sub-Tenant Name</param>
        /// <param name="tenantpassword">The password of the username invoking the call</param>
        /// <param name="tenantadmin">The username invoking the call</param>
        /// <returns>string variable (XML format)</returns>
        public string GetStorageConsumption(string tenant, string subtenant, string tenantadmin, string tenantpassword)
        {
            HttpWebResponse resp = null;
            try
            {
                string resource = "/sysmgmt/tenants/" + tenant + "/" + subtenant + "/scMetrics";
                Uri u = buildUrl(resource);
                HttpWebRequest con = (HttpWebRequest)WebRequest.Create(u);
                ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };

                // Build headers
                Dictionary<string, string> headers = new Dictionary<string, string>();

                headers.Add("x-atmos-tenantadminpassword", tenantpassword);
                headers.Add("x-atmos-tenantadmin", tenantadmin);
                headers.Add("x-atmos-authtype", "password");
                headers.Add("x-atmos-authsource", "local");


                // Add date
                string dateHeader = DateTime.Now.ToUniversalTime().ToString("r");
                log.TraceEvent(TraceEventType.Verbose, 0, "Date: " + dateHeader);
                headers.Add("Date", dateHeader);

                // Sign request
                signRequest(con, "GET", resource, headers);

                // Check response
                resp = (HttpWebResponse)con.GetResponse();
                int statInt = (int)resp.StatusCode;
                if (statInt > 299)
                {
                    handleError(resp);
                }

                byte[] response = readResponse(resp, null);
                string responseStr = Encoding.UTF8.GetString(response);
                log.TraceEvent(TraceEventType.Verbose, 0, "Response: " + responseStr);
                return responseStr;
            }
            catch (UriFormatException e)
            {
                throw new EsuException("Invalid URL", e);
            }
            catch (IOException e)
            {
                throw new EsuException("Error connecting to server", e);
            }
            catch (WebException e)
            {
                if (e.Response != null)
                {
                    handleError((HttpWebResponse)e.Response);
                }
                else
                {
                    throw new EsuException("Error executing request: " + e.Message, e);
                }
            }
            finally
            {
                if (resp != null)
                {
                    resp.Close();
                }
            }
            return null;;
        }
        //END ATMOS STORAGE CONSUMPTION
        #endregion

        #region Private Members
        private Uri buildUrl(string resource)
        {
            return new Uri(protocol + "://" + host + ":" + port + resource);
        }

        private void handleError(HttpWebResponse resp)
        {
            // Try and read the response body.
            try
            {
                byte[] response = readResponse(resp, null);
                string responseText = Encoding.UTF8.GetString(response);
                log.TraceEvent(TraceEventType.Verbose, 0, "Error response: " + responseText);
                XmlDocument d = new XmlDocument();
                d.LoadXml(responseText);

                if (d.GetElementsByTagName("Code").Count == 0 ||
                    d.GetElementsByTagName("Message").Count == 0)
                {
                    // not an error from ESU
                    throw new EsuException(resp.StatusDescription, (int)resp.StatusCode);
                }

                string code = d.GetElementsByTagName("Code").Item(0).InnerText;
                string message = d.GetElementsByTagName("Message").Item(0).InnerText;


                log.TraceEvent(TraceEventType.Verbose, 0, "Error: " + code + " message: " + message);
                throw new EsuException(message, int.Parse(code));

            }
            catch (IOException e)
            {
                log.TraceEvent(TraceEventType.Verbose, 0, "Could not read error response body: " + e);
                // Just throw what we know from the response
                try
                {
                    throw new EsuException(resp.StatusDescription, (int)resp.StatusCode);
                }
                catch (IOException e1)
                {
                    log.TraceEvent(TraceEventType.Verbose, 0, "Could not get response code/message: " + e1);
                    throw new EsuException("Could not get response code", e1);
                }
            }
            catch (XmlException e)
            {
                try
                {
                    log.TraceEvent(TraceEventType.Verbose, 0, "Could not parse response body for " +
                            resp.StatusCode + ": " + resp.StatusDescription + ": " +
                            e);
                    throw new EsuException("Could not parse response body for " +
                            resp.StatusCode + ": " + resp.StatusDescription,
                            e, (int)resp.StatusCode);
                }
                catch (IOException e1)
                {
                    throw new EsuException("Could not parse response body", e1);
                }

            }

        }


        private byte[] readResponse(HttpWebResponse resp, byte[] buffer)
        {
            Stream rs = resp.GetResponseStream();
            try
            {
                byte[] output;
                int contentLength = (int)resp.ContentLength;
                // If we know the content length, read it directly into a buffer.
                if (contentLength != -1)
                {
                    if (buffer != null && buffer.Length < contentLength)
                    {
                        throw new EsuException("The response buffer was not long enough to hold the response: " + buffer.Length + "<" + contentLength);
                    }
                    if (buffer != null)
                    {
                        output = buffer;
                    }
                    else
                    {
                        output = new byte[(int)resp.ContentLength];
                    }

                    int c = 0;
                    while (c < contentLength)
                    {
                        int read = rs.Read(output, c, contentLength - c);
                        if (read == 0)
                        {
                            // EOF!
                            throw new EsuException("EOF reading response at position " + c + " size " + (contentLength - c));
                        }
                        c += read;
                    }

                    return output;
                }
                else
                {
                    log.TraceEvent(TraceEventType.Verbose, 0, "Content length is unknown.  Buffering output.");
                    // Else, use a MemoryStream to collect the response.
                    if (buffer == null)
                    {
                        buffer = new byte[4096];
                    }
                    MemoryStream ms = new MemoryStream();
                    int c = 0;
                    while ((c = rs.Read(buffer, 0, buffer.Length)) != 0)
                    {
                        ms.Write(buffer, 0, c);
                    }
                    ms.Close();
                    return ms.ToArray();
                }
            }
            finally
            {
                rs.Close();
            }

        }

        private void signRequest(HttpWebRequest con, string method, string resource, Dictionary<string, string> headers)
        {
            // Build the string to hash.
            StringBuilder hashStr = new StringBuilder();
            hashStr.Append(method + "\n");

            // If content type exists, add it.  Otherwise add a blank line.
            if (headers.ContainsKey("Content-Type"))
            {
                log.TraceEvent(TraceEventType.Verbose, 0, "Content-Type: " + headers["Content-Type"]);
                hashStr.Append(headers["Content-Type"] + "\n");
            }
            else
            {
                hashStr.Append("\n");
            }

            // If the range header exists, add it.  Otherwise add a blank line.
            if (headers.ContainsKey("Range"))
            {
                hashStr.Append(headers["Range"] + "\n");
            }
            else
            {
                hashStr.Append("\n");
            }

            // Add the current date and the resource.
            hashStr.Append(headers["Date"] + "\n" +
                    resource.ToLower() + "\n");

            // Do the 'x-emc' headers.  The headers must be hashed in alphabetic
            // order and the values must be stripped of whitespace and newlines.
            List<string> keys = new List<string>();
            Dictionary<string, string> newheaders = new Dictionary<string, string>();

            // Extract the keys and values
            foreach (string key in headers.Keys)
            {
                if (key.IndexOf("x-emc") == 0)
                {
                    keys.Add(key.ToLower());
                    newheaders.Add(key.ToLower(), normalizeHeaderValue(headers[key]));
                }
            }

            // Sort the keys and add the headers to the hash string.
            keys.Sort();
            bool first = true;
            foreach (string key in keys)
            {
                if (!first)
                {
                    hashStr.Append("\n");
                }
                else
                {
                    first = false;
                }
                //this.trace( "xheader: " . k . "." . newheaders[k] );
                hashStr.Append(key + ':' + newheaders[key]);
            }

            string hashOut = sign(hashStr.ToString());

            // Can set all the headers, etc now.  Microsoft doesn't let you
            // set some of the headers directly.  Modify the headers through
            // reflection to get around this.
            MethodInfo m = con.Headers.GetType().GetMethod("AddWithoutValidate", BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.Instance, null,
                new Type[] { typeof(string), typeof(string) }, null);

            foreach (string name in headers.Keys)
            {
                log.TraceEvent(TraceEventType.Verbose, 0, "Setting " + name);
                m.Invoke(con.Headers, new object[] { name, headers[name] });
            }

            // Set the signature header
            con.Headers["x-emc-signature"] = hashOut;

            // Set the method.
            con.Method = method;

        }


        private string normalizeHeaderValue(string p)
        {
            p = p.Replace("\n", "");

            int len = p.Length;

            // Normalize consecutive spaces to one space.
            while (true)
            {
                p = p.Replace("  ", " ");
                if (p.Length == len)
                {
                    return p;
                }
                len = p.Length;
            }

        }

        private string sign(string hashStr)
        {
            log.TraceEvent(TraceEventType.Verbose, 0, "Hashing: \n" + hashStr);

            // Compute the signature hash
            HMACSHA1 mac = new HMACSHA1(secret);
            byte[] hashBytes = headerEncoder.GetBytes(hashStr.ToString());
            log.TraceEvent(TraceEventType.Verbose, 0, hashBytes.Length + " bytes to hash");
            mac.TransformFinalBlock(hashBytes, 0, hashBytes.Length);
            byte[] hashData = mac.Hash;
            log.TraceEvent(TraceEventType.Verbose, 0, hashData.Length + " bytes in signature");
            // Encode the hash in Base64.
            string hashOut = Convert.ToBase64String(hashData);

            log.TraceEvent(TraceEventType.Verbose, 0, "Hash: " + hashOut);
            return hashOut;
        }

        #endregion
    }
}
